#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import subprocess
import json
import time
import uuid
import rospy
import hashlib
import os.path
import socket
from std_msgs.msg import Int16

from vlc.srv import Play

__author__ = "yujikang (aoxun)"
__version__ = "0.0.1"
__license__ = "BSD"

try:  # attempt to use the Python 2 modules
    from urllib import urlencode
    from urllib2 import Request, urlopen, URLError, HTTPError
except ImportError:  # use the Python 3 modules
    from urllib.parse import urlencode
    from urllib.request import Request, urlopen
    from urllib.error import URLError, HTTPError

class RequestError(Exception): pass
class NoAudio(Exception): pass
class RecognitionError(Exception): pass

import speech_recognition as sr

# sudo apt-get install mpg321
def play_mp3(path):
    subprocess.Popen(['mpg321', '-q', path]).wait()

class ChatController(object):
    def __init__(self, baidu_id, baidu_secret, tuling_key, chat_timeout):
        self._baidu_id = baidu_id
        self._baidu_secret = baidu_secret
        self._tuling_key = tuling_key
        self._timeout = chat_timeout
        self._time = rospy.Duration(0)
        self._running = False
        self.error_count = 0
        self.max_error = 3

        self.auth_timeout = 3
        self.tuling_timeout = 3
        self.tts_timeout = 3
        self.recognizer_timeout = 3

        self._r = sr.Recognizer()
        self._r.operation_timeout = 3
        self._mircophone = sr.Microphone()

        rospy.Subscriber("/voice_angle", Int16, self._wakeup)
        rospy.wait_for_service('/play')
        self.play_service = rospy.ServiceProxy('play', Play)

    def _listen(self):
        audio = None
        print("A moment of silence, please...")
        with self._mircophone as source:
            self._r.adjust_for_ambient_noise(source)
        print("Set minimum energy threshold to {}".format(self._r.energy_threshold))
        with self._mircophone as source:
            try:
                audio = self._r.listen(source, 10)
            except sr.WaitTimeoutError as e:
                print("Oops {0}".format(e))

        you_said = None
        if not audio:
            return audio, you_said
            # return None
        try:
            recog_time = time.time()
            value = self._r.recognize_baidu(audio, self._baidu_id, self._baidu_secret, "zh", True)
            print("recog_time = %f" % (time.time() - recog_time))
            if str is bytes:  # this version of Python uses bytes for strings (Python 2)
                if "result" in value:
                    you_said = value["result"][0].encode('utf-8')
                else:
                    print("You said {}".format(value["err_msg"]))
                    self.error_count = self.error_count + 1                
            else:
                if "result" in value:
                    you_said = value["result"][0].encode('utf-8')
                else:
                    print("You said {}".format(value["err_msg"]))
                    self.error_count = self.error_count + 1
        except sr.UnknownValueError:
            # play_mp3('zoushen.mp3')
            print("Oops! Didn't catch that")
        except sr.RequestError as e:
            # play_mp3('zoushen.mp3')
            print("Uh oh! Couldn't request results from Baidu Speech Recognition service; {0}".format(e))

        return audio, you_said
        # robot_said = self._robot_said(you_said)

    def _robot_said(self, you_said):
        robot_said = None
        if not you_said:
            return robot_said

        url = "http://www.tuling123.com/openapi/api?{}".format(urlencode({
            "key": self._tuling_key,
            "info": you_said,
            "userid": uuid.uuid4(),
        }))
        request = Request(url, headers={
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "text/plain",
        })
        try:
            tuling_time = time.time()
            response = urlopen(request, timeout=self.tuling_timeout)
            print("tuling_time = %f" % (time.time() - tuling_time))
            response_text = response.read().decode("utf-8")
            result = json.loads(response_text)
            robot_said = result["text"].encode('utf8')
            print("Robot said {}".format(robot_said))
        except HTTPError as e:
            print("tuling request failed: {}".format(e.reason))
            pass
            # raise RequestError("recognition request failed: {}".format(e.reason))
        except URLError as e:
            print("tuling connection failed: {}".format(e.reason))
            pass
        except socket.error as e:
            print("tuling connection failed: {}".format(e))
            pass
            # raise RequestError("recognition connection failed: {}".format(e.reason))

        return robot_said

    def text_to_speech(self, robot_said, hash_str):
        if not robot_said:
            return

        self.update_token()

        url = "http://tsn.baidu.com/text2audio?{}".format(urlencode({
            "tex": robot_said,
            "lan": 'zh',
            "tok": self.baidu_cached_access_token,
            "ctp": 1,
            "cuid": uuid.uuid4(),
            "spd": 5,
            "pit": 5,
            "vol": 5,
            "per": 4,
        }))
        request = Request(url, headers={})
        try:
            tts_time = time.time()
            response = urlopen(request, timeout=self.tts_timeout)
            print("tts_time = %f" % (time.time() - tts_time))
            # print response.headers
            ofp = open(hash_str, "wb")
            ofp.write(response.read())
            ofp.close()
            self.play_service("talk.3gp")
            play_mp3(hash_str)
            self.play_service("look.3gp")
        except HTTPError as e:
            play_mp3('shuaige.mp3')
            print("tts request failed: {}".format(e.reason))
            # raise RequestError("recognition request failed: {}".format(e.reason))
        except URLError as e:
            play_mp3('shuaige.mp3')
            print("tts connection failed: {}".format(e.reason))
            # raise RequestError("recognition connection failed: {}".format(e.reason))
        except socket.error as e:
            play_mp3('shuaige.mp3')
            print("tts connection failed: {}".format(e))
            pass
    def update_token(self):
        access_token, expire_time = getattr(self, "baidu_cached_access_token", None), getattr(self, "baidu_cached_access_token_expiry", None)
        allow_caching = True
        try:
            from time import monotonic  # we need monotonic time to avoid being affected by system clock changes, but this is only available in Python 3.3+
        except ImportError:
            try:
                from monotonic import monotonic  # use time.monotonic backport for Python 2 if available (from https://pypi.python.org/pypi/monotonic)
            except (ImportError, RuntimeError):
                expire_time = None  # monotonic time not available, don't cache access tokens
                allow_caching = False  # don't allow caching, since monotonic time isn't available
        if expire_time is None or monotonic() > expire_time:  # caching not enabled, first credential request, or the access token from the previous one expired
            # get an access token using OAuth
            credential_url = "https://openapi.baidu.com/oauth/2.0/token?{}".format(urlencode({
                "grant_type": "client_credentials",
                "client_id": self._baidu_id,
                "client_secret": self._baidu_secret,
            }))
            credential_request = Request(credential_url, data=b"", headers={})

            if allow_caching:
                start_time = monotonic()
            try:
                credential_response = urlopen(credential_request, timeout=self.auth_timeout)
            except HTTPError as e:
                print("get token request failed: {}".format(e.reason))
                # raise RequestError("get token request failed: {}".format(e.reason))
            except URLError as e:
                print("get token connection failed: {}".format(e.reason))
            except socket.error as e:
                print("get token failed: {}".format(e))
                pass
                # raise RequestError("get token connection failed: {}".format(e.reason))
            response = credential_response.read().decode("utf-8")
            result = json.loads(response)
            access_token = result["access_token"]

            if allow_caching:
                # save the token for the duration it is valid for
                self.baidu_cached_access_token = access_token
                self.baidu_cached_access_token_expiry = start_time + (60 * 60 * 24 * 30)  # according to http://yuyin.baidu.com/docs/tts/135#%E8%8E%B7%E5%8F%96%20Access%20Token, the token expires in exactly 1 month

    def _wakeup(self, data):
        if self._running:
            return

        self.play_service("wakeup.3gp")
        play_mp3('nihao.mp3')
        self._running = True
        self.error_count = 0
        self.run()

    def run(self):
        audio = None
        you_said = None
        robot_said = None
        hash_str = None

        # try:
        audio, you_said = self._listen()
        if not audio:
            self.error_count = self.error_count + 1

        if you_said:
            robot_said = self._robot_said(you_said)
            if robot_said:
                h = hashlib.new('ripemd160')
                h.update(robot_said)
                hash_str = h.hexdigest()
                # self._find_exist_voice(robot_said)
                if os.path.exists(hash_str):
                    play_mp3(hash_str)
                else:
                    self.text_to_speech(robot_said, hash_str)
            else:
                play_mp3('shuaige.mp3')
        else:
            if audio:
                play_mp3('zoushen.mp3')
        print(self.error_count)
        # except NoAudio as e:
        #     self.error_count = self.error_count + 1


        if self.error_count >= self.max_error:
            self._running = False
            self.play_service("sad.3gp")
            return
        else:
            self.run()


if __name__ == '__main__':
    rospy.init_node('chat')
    ChatController("ZxyGAMuX4h3NA0bl6YDCuuw1", "VGvaQVbBWHLueQdtOXGap11lNLdyFrwy", "9edbb965da3fdd6e0b59a85cc513cf6c", 0)
    rospy.spin()
